feat(js,shared): add Prosopo Procaptcha as a CAPTCHA provider - #8944
feat(js,shared): add Prosopo Procaptcha as a CAPTCHA provider#8944forgetso wants to merge 8 commits into
Conversation
Adds 'prosopo' alongside 'turnstile' in CaptchaProvider and a new getProcaptchaToken that mirrors the invisible + smart flows from turnstile.ts. When displayConfig.captchaProvider is 'prosopo', clerk-js loads the Procaptcha bundle from js.prosopo.io and renders into the existing invisible/smart containers. Turnstile remains the default. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
@forgetso is attempting to deploy a commit to the Clerk Production Team on Vercel. A member of the Team first needs to authorize it. |
🦋 Changeset detectedLatest commit: 721452b The changes in this PR will be included in the next version bump. This PR includes changesets to release 23 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository YAML (base), Organization UI (inherited) Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (12)
🔗 Linked repositories identifiedCodeRabbit considers these linked repositories for cross-repo context during reviews:
🚧 Files skipped from review as they are similar to previous changes (9)
Included review availability: Your plan provides up to 4 included reviews per hour; 3 remain after this review. 📝 WalkthroughWalkthroughAdds Prosopo Procaptcha as an alternative CAPTCHA provider. A shared container resolver handles modal, smart, and invisible containers for both providers. Token generation routes by Estimated code review effort: 4 (Complex) | ~60 minutes Merge Risk: ⚪ Minimal · up to The change is merge-ready after normal checks, with one localized test-isolation cleanup to restore real timers after each test; no actionable merge-blocking risk remains. Changes
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
Full details: Docstring CoverageExplanation Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 5 functions across 11 files. (1 skipped: 1 unsupported.)
✨ Finishing Touches 💡 1🛠️ Fix failing CI checks 💡
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Pull the modal/smart/invisible container decision out of turnstile.ts and prosopo.ts into a single resolveCaptchaContainer helper, plus a matching cleanupCaptchaContainer for the side effects (modal close, invisible div removal). Each provider keeps its own visual styling (Turnstile's maxHeight/min-height dance, Prosopo's reserved height) and its own render() call. Also: - Point the Procaptcha load-failure warning at js.prosopo.io + Prosopo CSP docs rather than the Cloudflare-flavored guidance. - Add router test for getCaptchaToken (turnstile vs prosopo dispatch). - Add CaptchaChallenge test that .invisible() and .managedOrInvisible() both honour displayConfig.captchaProvider. - Expand prosopo.test.ts with smart, modal, modal-not-ready, error- callback, expired-callback, and script-load-failure cases. Validates the warning now points at Prosopo's CSP docs. 15 captcha tests pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The Procaptcha runtime calls error-callback with an Error built from the underlying challenge failure (procaptcha/modules/Manager.ts:193 and procaptcha-frictionless/ProcaptchaFrictionless.tsx:222,240 both call events.onError(new Error(message)), which defaultCallbacks.ts:129 forwards to the user-supplied error-callback). Earlier draft of this file assumed no argument was passed and rejected with a hardcoded 'procaptcha_error'. Type the callback the way the runtime actually behaves and surface error.message, with the previous identifier kept as a defensive fallback. New test asserts both branches. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/clerk-js/src/utils/captcha/containerResolver.ts`:
- Around line 91-97: The invisible CAPTCHA container resolver creates containers
with a shared class name selector that causes interference when multiple CAPTCHA
instances run concurrently. Instead of using the shared
CAPTCHA_INVISIBLE_CLASSNAME class selector, generate a unique identifier per
instance (such as a data attribute with a generated ID) for the created div
element, then return that unique selector in the containerSelector field. Update
the callers in turnstile.ts and prosopo.ts to pass the resolved unique container
selector into their cleanup functions instead of relying on the shared class
query.
- Around line 63-64: The waitForElement call for modalContainerQuerySelector has
no timeout and can hang indefinitely if the modal container never appears. Wrap
the waitForElement promise with a timeout mechanism that rejects after a
reasonable duration if the element is not found, and throw a captchaError with
an appropriate error message when the timeout is exceeded. This ensures the auth
flow fails fast rather than hanging.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository YAML (base), Repository UI (inherited)
Review profile: CHILL
Plan: Pro
Run ID: f69936f1-602b-4123-a553-15e3b7761394
📒 Files selected for processing (10)
.changeset/prosopo-captcha-provider.mdpackages/clerk-js/src/utils/captcha/CaptchaChallenge.tspackages/clerk-js/src/utils/captcha/__tests__/CaptchaChallenge.test.tspackages/clerk-js/src/utils/captcha/__tests__/getCaptchaToken.test.tspackages/clerk-js/src/utils/captcha/__tests__/prosopo.test.tspackages/clerk-js/src/utils/captcha/containerResolver.tspackages/clerk-js/src/utils/captcha/getCaptchaToken.tspackages/clerk-js/src/utils/captcha/prosopo.tspackages/clerk-js/src/utils/captcha/turnstile.tspackages/shared/src/types/displayConfig.ts
- waitForElement on the modal container is unbounded, so a missing modal
container could hang the auth flow indefinitely. Race against a 5s
timeout and throw { captchaError: 'modal_container_not_found' } on
expiry.
- The invisible flow used a shared .clerk-invisible-captcha class
selector for both render and cleanup. Two concurrent challenges would
render into the same node and the second cleanup could remove the
first's container. Mint a per-instance id ('<class>-<ts>-<n>'),
return that id as the selector, and pass it into cleanup so each
challenge only removes its own node.
- New containerResolver.test.ts covers the timeout path and asserts
concurrent invisible challenges resolve to distinct containers and
clean up independently. Existing turnstile.test.ts updated to match
the new id-based selector format (the element still carries the
class).
Both issues were pre-existing in turnstile.ts and inherited by the
refactor; flagged by CodeRabbit on PR clerk#8944.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/clerk-js/src/utils/captcha/containerResolver.ts`:
- Around line 129-133: The exported function cleanupCaptchaContainer is missing
an explicit return type annotation. Add `: void` to the function signature after
the closing parenthesis of the parameter list to comply with the coding
guideline requiring explicit return types for exported functions.
- Around line 71-79: The MutationObserver created inside the waitForElement
function is not being cleaned up when the Promise.race timeout branch resolves
first, causing the observer to remain connected to document.body indefinitely.
Fix this by either modifying the waitForElement function to accept an abort
signal that it uses to clean up the observer when signaled, then create an
AbortController in the calling code and trigger it after the Promise.race
resolves with a timeout result, or alternatively add explicit cleanup logic in
the calling code after the race resolves to properly stop and disconnect the
observer that was created inside waitForElement. The key is ensuring the
MutationObserver is disconnected regardless of whether the race completes due to
element found or timeout.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository YAML (base), Repository UI (inherited)
Review profile: CHILL
Plan: Pro
Run ID: 42e2fd3e-0ea0-4c52-9117-79f7964a9322
📒 Files selected for processing (5)
packages/clerk-js/src/utils/captcha/__tests__/containerResolver.test.tspackages/clerk-js/src/utils/captcha/__tests__/turnstile.test.tspackages/clerk-js/src/utils/captcha/containerResolver.tspackages/clerk-js/src/utils/captcha/prosopo.tspackages/clerk-js/src/utils/captcha/turnstile.ts
🚧 Files skipped from review as they are similar to previous changes (2)
- packages/clerk-js/src/utils/captcha/turnstile.ts
- packages/clerk-js/src/utils/captcha/prosopo.ts
| // waitForElement never rejects, so race it against a timeout to keep the auth flow from hanging. | ||
| const el = await Promise.race<Element | null>([ | ||
| waitForElement(modalContainerQuerySelector), | ||
| new Promise<null>(resolve => setTimeout(() => resolve(null), MODAL_CONTAINER_TIMEOUT_MS)), | ||
| ]); | ||
| if (!el) { | ||
| // eslint-disable-next-line @typescript-eslint/only-throw-error | ||
| throw { captchaError: 'modal_container_not_found' }; | ||
| } |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟡 Minor
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Inspect waitForElement implementation for observer cleanup / abort support
rg -nP --type=ts -C8 'waitForElement' packages/shared/srcRepository: clerk/javascript
Length of output: 1219
🏁 Script executed:
cat -n packages/shared/src/dom/waitForElement.tsRepository: clerk/javascript
Length of output: 896
🏁 Script executed:
cat -n packages/clerk-js/src/utils/captcha/containerResolver.ts | sed -n '65,85p'Repository: clerk/javascript
Length of output: 1325
Fix waitForElement observer cleanup on timeout.
The waitForElement promise remains unsettled when the timeout branch wins in Promise.race, leaving the MutationObserver connected to document.body indefinitely. Each failed modal mount leaks an observer that continues to fire on DOM mutations for the rest of the page session.
Either add abort signal/timeout support to waitForElement so it cleans up on timeout, or explicitly clean up the observer in the calling code after the race resolves.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/clerk-js/src/utils/captcha/containerResolver.ts` around lines 71 -
79, The MutationObserver created inside the waitForElement function is not being
cleaned up when the Promise.race timeout branch resolves first, causing the
observer to remain connected to document.body indefinitely. Fix this by either
modifying the waitForElement function to accept an abort signal that it uses to
clean up the observer when signaled, then create an AbortController in the
calling code and trigger it after the Promise.race resolves with a timeout
result, or alternatively add explicit cleanup logic in the calling code after
the race resolves to properly stop and disconnect the observer that was created
inside waitForElement. The key is ensuring the MutationObserver is disconnected
regardless of whether the race completes due to element found or timeout.
| export const cleanupCaptchaContainer = ( | ||
| containerType: CaptchaContainerType, | ||
| opts: Pick<CaptchaOptions, 'closeModal'>, | ||
| containerSelector?: string, | ||
| ) => { |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Add an explicit : void return type.
cleanupCaptchaContainer is an exported function but omits its return type.
♻️ Proposed change
export const cleanupCaptchaContainer = (
containerType: CaptchaContainerType,
opts: Pick<CaptchaOptions, 'closeModal'>,
containerSelector?: string,
-) => {
+): void => {As per coding guidelines: "Always define explicit return types for functions, especially public APIs", and based on learnings to enforce explicit return type annotations for exported functions.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/clerk-js/src/utils/captcha/containerResolver.ts` around lines 129 -
133, The exported function cleanupCaptchaContainer is missing an explicit return
type annotation. Add `: void` to the function signature after the closing
parenthesis of the parameter list to comply with the coding guideline requiring
explicit return types for exported functions.
Sources: Coding guidelines, Learnings
|
Note GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer. |
|
Any thoughts here @nikosdouvlis? |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
packages/clerk-js/src/utils/captcha/__tests__/containerResolver.test.ts (1)
51-51: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winUse typed mock references in both test assertions.
Both changed lines use
anyto access or configure mocks. This can hide drift in the mocked APIs.
packages/clerk-js/src/utils/captcha/__tests__/containerResolver.test.ts#L51-L51: Usevi.mocked(waitForElement)with a type derived fromwaitForElement.packages/clerk-js/src/utils/captcha/__tests__/turnstile.test.ts#L56-L56: Capture the typedrendermock or add a narrowWindowaugmentation instead of using(window as any).As per coding guidelines, avoid
anyin TypeScript and prefer narrowed types.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/clerk-js/src/utils/captcha/__tests__/containerResolver.test.ts` at line 51, Replace the any-based mock access in packages/clerk-js/src/utils/captcha/__tests__/containerResolver.test.ts:51-51 with vi.mocked(waitForElement), using its inferred or explicitly derived type. In packages/clerk-js/src/utils/captcha/__tests__/turnstile.test.ts:56-56, capture a typed render mock or add a narrow Window augmentation, removing the window as any cast while preserving the existing test behavior.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/clerk-js/src/utils/captcha/__tests__/containerResolver.test.ts`:
- Around line 20-23: Update the existing afterEach cleanup in containerResolver
tests to call vi.useRealTimers() alongside vi.restoreAllMocks(), ensuring fake
timers are restored even when an awaited rejection exits early. Remove the later
success-path-only timer restoration if it becomes redundant.
In `@packages/clerk-js/src/utils/captcha/containerResolver.ts`:
- Around line 101-103: Update the fallback message in the Smart CAPTCHA
initialization path near the relevant console.error call to describe the Prosopo
configuration, including captcha_provider: "prosopo" and its configuration path,
before linking to the custom-flow CAPTCHA documentation.
---
Nitpick comments:
In `@packages/clerk-js/src/utils/captcha/__tests__/containerResolver.test.ts`:
- Line 51: Replace the any-based mock access in
packages/clerk-js/src/utils/captcha/__tests__/containerResolver.test.ts:51-51
with vi.mocked(waitForElement), using its inferred or explicitly derived type.
In packages/clerk-js/src/utils/captcha/__tests__/turnstile.test.ts:56-56,
capture a typed render mock or add a narrow Window augmentation, removing the
window as any cast while preserving the existing test behavior.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository YAML (base), Organization UI (inherited)
Review profile: CHILL
Plan: Pro Plus
Run ID: e64960b0-f94b-4e9e-bd37-5b933698e9fb
📒 Files selected for processing (12)
.changeset/prosopo-captcha-provider.mdpackages/clerk-js/src/utils/captcha/CaptchaChallenge.tspackages/clerk-js/src/utils/captcha/__tests__/CaptchaChallenge.test.tspackages/clerk-js/src/utils/captcha/__tests__/containerResolver.test.tspackages/clerk-js/src/utils/captcha/__tests__/getCaptchaToken.test.tspackages/clerk-js/src/utils/captcha/__tests__/prosopo.test.tspackages/clerk-js/src/utils/captcha/__tests__/turnstile.test.tspackages/clerk-js/src/utils/captcha/containerResolver.tspackages/clerk-js/src/utils/captcha/getCaptchaToken.tspackages/clerk-js/src/utils/captcha/prosopo.tspackages/clerk-js/src/utils/captcha/turnstile.tspackages/shared/src/types/displayConfig.ts
🔗 Linked repositories identified
CodeRabbit considers these linked repositories for cross-repo context during reviews:
clerk/clerk_go(manual)clerk/dashboard(manual)clerk/accounts(manual)clerk/backoffice(manual)clerk/clerk(manual)clerk/clerk-docs(manual)clerk/cloudflare-workers(manual)
🚧 Files skipped from review as they are similar to previous changes (8)
- packages/shared/src/types/displayConfig.ts
- packages/clerk-js/src/utils/captcha/tests/CaptchaChallenge.test.ts
- packages/clerk-js/src/utils/captcha/tests/getCaptchaToken.test.ts
- packages/clerk-js/src/utils/captcha/getCaptchaToken.ts
- .changeset/prosopo-captcha-provider.md
- packages/clerk-js/src/utils/captcha/CaptchaChallenge.ts
- packages/clerk-js/src/utils/captcha/turnstile.ts
- packages/clerk-js/src/utils/captcha/prosopo.ts
| afterEach(() => { | ||
| document.body.innerHTML = ''; | ||
| vi.restoreAllMocks(); | ||
| }); |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Locate file =="
fd -a 'containerResolver.test.ts' . || true
echo "== File excerpt =="
file="$(fd 'containerResolver.test.ts' . | head -n 1)"
if [ -n "${file:-}" ]; then
wc -l "$file"
cat -n "$file"
fi
echo "== Relevant timer usages =="
file="$(fd 'containerResolver.test.ts' . | head -n 1 || true)"
if [ -n "${file:-}" ]; then
rg -n "vi\.use(Fake|Real)Timers|waitForElement|await" "$file"
fi
echo "== Nearby tests summary =="
python3 - <<'PY'
from pathlib import Path
p = next(Path('.').rglob('containerResolver.test.ts'), None)
if not p:
raise SystemExit
print(p)
text = p.read_text()
for i, line in enumerate(text.splitlines(), 1):
if 'it(' in line or "vi.useFakeTimers" in line or "vi.useRealTimers" in line or 'afterEach' in line or 'waitForElement' in line:
print(f"{i}: {line}")
PYRepository: clerk/javascript
Length of output: 5024
🌐 Web query:
Vitest vi.useFakeTimers beforeEach afterEach useRealTimers documentation
💡 Result:
In Vitest, you use fake timers to gain control over time-dependent functions like setTimeout, setInterval, and Date, allowing you to run tests instantly instead of waiting for real time to pass [1][2]. Key functions and their usage: vi.useFakeTimers: Enables fake timers [3][2]. It is best practice to call this in a beforeEach hook to ensure a clean state for each test [1][4]. It intercepts calls to global timer APIs and, optionally, other platform methods like nextTick or queueMicrotask [2][5]. vi.useRealTimers: Restores the native timer implementations [3][2]. It is highly recommended to call this in an afterEach hook to avoid leaking mocked timer behavior into subsequent tests, which can cause instability or unexpected test failures [4]. Example Pattern: import { beforeEach, afterEach, describe, it, vi } from 'vitest'; describe('timer tests', => { beforeEach( => { // Enable fake timers before the test runs vi.useFakeTimers; }); afterEach( => { // Restore real timers after each test to prevent side effects vi.useRealTimers; }); it('example test', => { // Test logic here }); }); Important Considerations: - Date Mocking: vi.useFakeTimers also mocks the global Date object [3][2]. If you use vi.setSystemTime to control the date, remember that this state persists unless you manually reset it or restore real timers [6][7]. - Configuration: You can configure which APIs are faked (e.g., nextTick, requestAnimationFrame) by passing an options object to vi.useFakeTimers({ toFake: [...] }) [2][5]. - Cleanup: Always ensure cleanup occurs in afterEach, even if a test fails, to maintain a consistent environment for the rest of your test suite [4].
Citations:
- 1: https://vitest.dev/guide/mocking/timers
- 2: https://vitest.dev/api/vi
- 3: https://github.com/vitest-dev/vitest/blob/v4.1.9/docs/api/vi.md
- 4: https://qaskills.sh/blog/vitest-fake-timers-date-testing-guide
- 5: https://github.com/vitest-dev/vitest/blob/206e8cff/docs/config/faketimers.md
- 6: https://vitest.dev/guide/mocking
- 7: https://github.com/vitest-dev/vitest/blob/main/docs/guide/mocking.md
Restore fake timers in afterEach.
This test enables fake timers, but vi.useRealTimers() only runs after the awaited rejection expectation. If the awaited promise rejects before that line runs, fake timers remain enabled and can affect later tests. Move vi.useRealTimers() into the existing afterEach block.
Proposed fix
afterEach(() => {
document.body.innerHTML = '';
+ vi.useRealTimers();
vi.restoreAllMocks();
});
...
- vi.useRealTimers();📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| afterEach(() => { | |
| document.body.innerHTML = ''; | |
| vi.restoreAllMocks(); | |
| }); | |
| afterEach(() => { | |
| document.body.innerHTML = ''; | |
| vi.useRealTimers(); | |
| vi.restoreAllMocks(); | |
| }); |
🧰 Tools
🪛 ast-grep (0.45.0)
[warning] 20-20: Direct modification of innerHTML or outerHTML properties detected. Modifying these properties with unsanitized user input can lead to XSS vulnerabilities. Use safe alternatives or sanitize content first.
Context: document.body.innerHTML = ''
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation
(dom-content-modification)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/clerk-js/src/utils/captcha/__tests__/containerResolver.test.ts`
around lines 20 - 23, Update the existing afterEach cleanup in containerResolver
tests to call vi.useRealTimers() alongside vi.restoreAllMocks(), ensuring fake
timers are restored even when an awaited rejection exits early. Remove the later
success-path-only timer restoration if it becomes redundant.
| console.error( | ||
| 'Cannot initialize Smart CAPTCHA widget because the `clerk-captcha` DOM element was not found; falling back to Invisible CAPTCHA widget. If you are using a custom flow, visit https://clerk.com/docs/guides/development/custom-flows/authentication/bot-sign-up-protection for instructions', | ||
| ); |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Update the linked CAPTCHA documentation for Prosopo.
The fallback message directs developers to documentation that still states Cloudflare Turnstile is the CAPTCHA provider. Describe captcha_provider: "prosopo" and its configuration path before directing Prosopo users to this page.
As per coding guidelines, “Update documentation for API changes.”
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/clerk-js/src/utils/captcha/containerResolver.ts` around lines 101 -
103, Update the fallback message in the Smart CAPTCHA initialization path near
the relevant console.error call to describe the Prosopo configuration, including
captcha_provider: "prosopo" and its configuration path, before linking to the
custom-flow CAPTCHA documentation.
Sources: Coding guidelines, Linked repositories
|
Note GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer. |
Hey @tmilewski @alexcarpenter. This PR adds Prosopo as an opt-in alternative to Turnstile, behind
displayConfig.captchaProvider.Why
Prosopo is has EU data residency and advanced bot detection - harder to bypass than default Cloudflare Turnstile.
What
CaptchaProviderwidened to'turnstile' | 'prosopo'(default unchanged).getProcaptchaToken;getCaptchaTokendispatches by provider.resolveCaptchaContainerhelper used by both providers.CaptchaChallenge.invisible()now readscaptchaProviderfromdisplayConfiginstead of hardcoding'turnstile'.Tests
16 unit tests, mocked
window.procaptcha(same pattern as the existing Turnstile suite — neither hits a live CDN).Backend dependency
Needs
display_config.captcha_provider: "prosopo"from FAPI to actually fire. Frontend half lands first; happy to coordinate on the backend side.Please let me know your thoughts.